package com.lagou.factory;

import com.lagou.annotation.AutowiredZdy;
import com.lagou.annotation.RepositoryZdy;
import com.lagou.annotation.ServiceZdy;
import com.lagou.annotation.TransactionalZdy;
import com.lagou.service.TransferService;
import com.lagou.utils.ClassUtil;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

/**
 * Bean工厂类：用于生产对象（使用反射技术）
 *
 *
 * @author: liufh
 * @create: 2021-07-11 15:42
 **/
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */

    public static HashMap<String, Object> map = new HashMap<>();

    static {
        InputStream inputStream = BeanFactory.class.getClassLoader().getResourceAsStream("applicationContext.xml");
        try {
            Document document = new SAXReader().read(inputStream);
            Element rootElement = document.getRootElement();

            //任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
            analysisByXml(rootElement);

            //优化：注解方式解析Bean
            analysisByAnnotation(rootElement);

        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */
    public static Object getBean(String id) {
        return map.get(id);
    }

    /**
     * XML方式解析Bean
     */
    public static void analysisByXml(Element rootElement) {
        try {
            List<Element> beansList = rootElement.selectNodes("//bean");
            for (Element bean : beansList) {
                //处理每个bean元素，获取到该元素的id 和 class 属性
                String id = bean.attributeValue("id");
                String className = bean.attributeValue("class");
                //通过反射技术实例化对象
                Class<?> aClass = Class.forName(className);
                Object o = aClass.newInstance();
                // 存储到map中待用
                map.put(id, o);
            }

            // 实例化完成之后维护对象的依赖关系，检查哪些对象需要传值进入，根据它的配置，我们传入相应的值
            // 有property子元素的bean就有传值需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            for (Element property : propertyList) {
                String name = property.attributeValue("name");
                String ref = property.attributeValue("ref");
                //获取父元素
                Element parentBean = property.getParent();
                String parentBeanId = parentBean.attributeValue("id");
                Object parentBeanObj = map.get(parentBeanId);
                //调用父元素对象的反射功能，并遍历对象中的所有方法找到"set"+name
                Method[] methods = parentBeanObj.getClass().getMethods();
                for (Method method : methods) {
                    if (method.getName().equalsIgnoreCase("set"+name)) {
                        method.invoke(parentBeanObj, map.get(ref));
                    }
                }
                //把处理之后的parentObject重新放到map中
                map.put(parentBeanId, parentBeanObj);
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 注解方式解析Bean
     */
    public static void analysisByAnnotation(Element rootElement) {
        try {
            //优化：注解方式
            //获取扫描路径
            Element componentScanBean = (Element) rootElement.selectNodes("component-scan").get(0);
            String basePackage = componentScanBean.attributeValue("base-package");
            //获取路径下所有类
            Set<Class<?>> classSet = ClassUtil.getClassSet(basePackage);
            //1.先实例化
            for (Class<?> aClass : classSet) {
                //判断类上是否带有注解，有则存储到map中待用
                ServiceZdy service = aClass.getAnnotation(ServiceZdy.class);
                RepositoryZdy repository = aClass.getAnnotation(RepositoryZdy.class);
                if (service != null || repository != null) {
                    Object o = aClass.newInstance();
                    //若注解无value属性值，则取接口首字母小写
                    String className = getAnnotationValue(service, aClass);
                    map.put(className, o);
                }
            }
            //2.再维护对象的依赖关系
            for (Class<?> aClass : classSet) {
                ServiceZdy service = aClass.getAnnotation(ServiceZdy.class);
                RepositoryZdy repository = aClass.getAnnotation(RepositoryZdy.class);
                if (service == null && repository == null) {
                    continue;
                }
                //获取接口名(若注解无value属性值，则取接口首字母小写)
                String className = getAnnotationValue(service, aClass);
                Object parentBeanObj = map.get(className);
                //判断是否需要注入
                Field[] declaredFields = aClass.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    AutowiredZdy autowiredAnnotation = declaredField.getAnnotation(AutowiredZdy.class);
                    if (autowiredAnnotation == null) {
                        continue;
                    }
                    String name = declaredField.getName();
                    Method[] methods = parentBeanObj.getClass().getMethods();
                    for (Method method : methods) {
                        if (method.getName().equalsIgnoreCase("set"+name)) {
                            method.invoke(parentBeanObj, map.get(name));
                            break;
                        }
                    }
                }

                //判断是否需要代理（初级版：只判断方法是否需要代理）
                Method[] methods = aClass.getMethods();
                for (Method method : methods) {
                    TransactionalZdy transactionalAnnotation = method.getAnnotation(TransactionalZdy.class);
                    if (transactionalAnnotation == null) {
                        continue;
                    }
                    //首先从BeanFactory获取到proxyFactory代理工厂的实例化对象
                    ProxyFactory proxyFactory = (ProxyFactory) map.get("proxyFactory");
                    //再从代理工厂获取代理对象（此代理对象作用：增强事务的控制）
                    Class<?>[] interfaces = aClass.getInterfaces();
                    if (interfaces.length > 0) {
                        //实现了接口，使用JDK动态代理
                        parentBeanObj = proxyFactory.getJdkProxy(parentBeanObj);
                    } else {
                        //没有实现接口，使用cglib动态代理
                        parentBeanObj = proxyFactory.getCglibProxy(parentBeanObj);
                    }
                    break;
                }

                //把处理之后的parentObject重新放到map中
                map.put(className, parentBeanObj);
            }

        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取注解value，若注解无value属性值，则取接口首字母小写
     */
    public static String getAnnotationValue(Object annotation, Class<?> aClass) {
        String className = null;
        if (annotation instanceof ServiceZdy) {
            className = ((ServiceZdy)annotation).value();
        } else if (annotation instanceof RepositoryZdy) {
            className = ((RepositoryZdy)annotation).value();
        }
        if (StringUtils.isBlank(className)) {
            Class<?>[] interfaces = aClass.getInterfaces();
            for (Class<?> anInterface : interfaces) {
                className = lowerFirstCase(anInterface.getSimpleName());
            }
        }
        return className;
    }

    /**
     * 首字母小写
     */
    public static String lowerFirstCase(String str){
        char[] chars = str.toCharArray();
        //首字母小写方法，大写会变成小写，如果小写首字母会消失
        chars[0] +=32;
        return String.valueOf(chars);
    }

    /**
     * 首字母大写
     */
    public static String upperFirstCase(String str){
        char[] chars = str.toCharArray();
        //首字母小写方法，大写会变成小写，如果小写首字母会消失
        chars[0] -=32;
        return String.valueOf(chars);
    }

}
